package service

import (
	"fmt"
	"sync"
	"time"

	"github.com/sirupsen/logrus"
)

// ConnectionInfo holds metadata about a connection
type ConnectionInfo struct {
	Name       string    `json:"name"`
	URI        string    `json:"uri"`
	Connected  bool      `json:"connected"`
	IsDefault  bool      `json:"is_default"`
	CreatedAt  time.Time `json:"created_at"`
	LastActive time.Time `json:"last_active"`
}

// ConnectionManager manages multiple libvirt connections
type ConnectionManager struct {
	connections map[string]*Connection
	defaultURI  string
	mu          sync.RWMutex
	log         *logrus.Logger
}

// NewConnectionManager creates a new connection manager
func NewConnectionManager(log *logrus.Logger) *ConnectionManager {
	return &ConnectionManager{
		connections: make(map[string]*Connection),
		log:         log,
	}
}

// AddConnection adds a new connection
func (cm *ConnectionManager) AddConnection(name, uri string) error {
	cm.mu.Lock()
	defer cm.mu.Unlock()

	// Check if connection already exists
	if _, exists := cm.connections[name]; exists {
		return fmt.Errorf("connection %s already exists", name)
	}

	// Create new connection with timeout
	connCh := make(chan *Connection, 1)
	errCh := make(chan error, 1)

	go func() {
		conn, err := NewConnection(uri)
		if err != nil {
			errCh <- err
			return
		}
		connCh <- conn
	}()

	select {
	case conn := <-connCh:
		cm.connections[name] = conn
		cm.log.WithFields(logrus.Fields{
			"name": name,
			"uri":  uri,
		}).Info("Connection added successfully")
		return nil
	case err := <-errCh:
		return fmt.Errorf("failed to create connection %s: %w", name, err)
	case <-time.After(30 * time.Second):
		return fmt.Errorf("timeout while creating connection %s", name)
	}
}

// RemoveConnection removes a connection
func (cm *ConnectionManager) RemoveConnection(name string) error {
	cm.mu.Lock()
	defer cm.mu.Unlock()

	conn, exists := cm.connections[name]
	if !exists {
		return fmt.Errorf("connection %s not found", name)
	}

	// Don't allow removing the default connection if it's the last one
	if name == cm.defaultURI && len(cm.connections) == 1 {
		return fmt.Errorf("cannot remove the only connection")
	}

	// Close connection
	if err := conn.Close(); err != nil {
		cm.log.WithError(err).Warn("Error closing connection")
	}

	delete(cm.connections, name)
	cm.log.WithField("name", name).Info("Connection removed")

	return nil
}

// GetConnection retrieves a connection by name
func (cm *ConnectionManager) GetConnection(name string) (*Connection, error) {
	cm.mu.RLock()
	defer cm.mu.RUnlock()

	conn, exists := cm.connections[name]
	if !exists {
		return nil, fmt.Errorf("connection %s not found", name)
	}

	return conn, nil
}

// GetDefaultConnection returns the default connection
func (cm *ConnectionManager) GetDefaultConnection() (*Connection, error) {
	cm.mu.RLock()
	defer cm.mu.RUnlock()

	if cm.defaultURI == "" {
		return nil, fmt.Errorf("no default connection set")
	}

	conn, exists := cm.connections[cm.defaultURI]
	if !exists {
		return nil, fmt.Errorf("default connection not found")
	}

	return conn, nil
}

// SetDefaultConnection sets the default connection
func (cm *ConnectionManager) SetDefaultConnection(name string) error {
	cm.mu.Lock()
	defer cm.mu.Unlock()

	if _, exists := cm.connections[name]; !exists {
		return fmt.Errorf("connection %s not found", name)
	}

	cm.defaultURI = name
	cm.log.WithField("name", name).Info("Default connection updated")

	return nil
}

// ListConnections returns all connection info
func (cm *ConnectionManager) ListConnections() []ConnectionInfo {
	cm.mu.RLock()
	defer cm.mu.RUnlock()

	infos := make([]ConnectionInfo, 0, len(cm.connections))

	for name, conn := range cm.connections {
		info := ConnectionInfo{
			Name:      name,
			URI:       conn.uri,
			Connected: conn.IsAlive(),
			IsDefault: name == cm.defaultURI,
			CreatedAt: time.Now(), // In production, store this when created
		}
		infos = append(infos, info)
	}

	return infos
}

// TestConnection tests a connection without adding it
func (cm *ConnectionManager) TestConnection(uri string) error {
	conn, err := NewConnection(uri)
	if err != nil {
		return fmt.Errorf("connection test failed: %w", err)
	}
	defer conn.Close()

	// Try to get version to verify connection
	if _, err := conn.GetVersion(); err != nil {
		return fmt.Errorf("connection test failed: %w", err)
	}

	return nil
}

// ReconnectAll attempts to reconnect all disconnected connections
func (cm *ConnectionManager) ReconnectAll() error {
	cm.mu.Lock()
	defer cm.mu.Unlock()

	var errors []string

	for name, conn := range cm.connections {
		if !conn.IsAlive() {
			cm.log.WithField("name", name).Info("Attempting to reconnect")
			if err := conn.Reconnect(); err != nil {
				errMsg := fmt.Sprintf("%s: %v", name, err)
				errors = append(errors, errMsg)
				cm.log.WithError(err).WithField("name", name).Error("Reconnection failed")
			} else {
				cm.log.WithField("name", name).Info("Reconnected successfully")
			}
		}
	}

	if len(errors) > 0 {
		return fmt.Errorf("reconnection errors: %v", errors)
	}

	return nil
}

// CloseAll closes all connections
func (cm *ConnectionManager) CloseAll() error {
	cm.mu.Lock()
	defer cm.mu.Unlock()

	var errors []string

	for name, conn := range cm.connections {
		if err := conn.Close(); err != nil {
			errors = append(errors, fmt.Sprintf("%s: %v", name, err))
		}
	}

	if len(errors) > 0 {
		return fmt.Errorf("errors closing connections: %v", errors)
	}

	return nil
}

// CloseConnection closes a specific connection without removing it from the manager
func (cm *ConnectionManager) CloseConnection(name string) error {
	cm.mu.RLock()
	defer cm.mu.RUnlock()

	conn, exists := cm.connections[name]
	if !exists {
		return fmt.Errorf("connection %s not found", name)
	}

	if err := conn.Close(); err != nil {
		cm.log.WithError(err).WithField("name", name).Error("Failed to close connection")
		return fmt.Errorf("failed to close connection %s: %w", name, err)
	}

	cm.log.WithField("name", name).Info("Connection closed successfully")
	return nil
}

// GetConnectionByURI finds a connection by its URI
func (cm *ConnectionManager) GetConnectionByURI(uri string) (*Connection, error) {
	cm.mu.RLock()
	defer cm.mu.RUnlock()

	for _, conn := range cm.connections {
		if conn.uri == uri {
			return conn, nil
		}
	}

	return nil, fmt.Errorf("connection with URI %s not found", uri)
}

// HasConnection checks if a connection exists
func (cm *ConnectionManager) HasConnection(name string) bool {
	cm.mu.RLock()
	defer cm.mu.RUnlock()

	_, exists := cm.connections[name]
	return exists
}

// GetConnectionCount returns the number of connections
func (cm *ConnectionManager) GetConnectionCount() int {
	cm.mu.RLock()
	defer cm.mu.RUnlock()

	return len(cm.connections)
}
